// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/cast/test/fake_media_source.h"

#include <utility>

#include "base/bind.h"
#include "base/files/scoped_file.h"
#include "base/logging.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "media/base/audio_buffer.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_fifo.h"
#include "media/base/audio_timestamp_helper.h"
#include "media/base/media.h"
#include "media/base/video_frame.h"
#include "media/base/video_util.h"
#include "media/cast/cast_sender.h"
#include "media/cast/test/utility/audio_utility.h"
#include "media/cast/test/utility/video_utility.h"
#include "ui/gfx/geometry/size.h"

// TODO(miu): Figure out why _mkdir() and _rmdir() are missing when compiling
// third_party/ffmpeg/libavformat/os_support.h (lines 182, 183).
// http://crbug.com/572986
#if defined(OS_WIN)
#include <direct.h>
#include "media/ffmpeg/ffmpeg_common.h"
#include "media/ffmpeg/ffmpeg_deleters.h"
#include "media/filters/ffmpeg_glue.h"
#include "media/filters/in_memory_url_protocol.h"
#else
#include "media/ffmpeg/ffmpeg_common.h"
#include "media/ffmpeg/ffmpeg_deleters.h"
#include "media/filters/ffmpeg_glue.h"
#include "media/filters/in_memory_url_protocol.h"
#endif // defined(OS_WIN)

namespace {

static const int kSoundFrequency = 440; // Frequency of sinusoid wave.
static const float kSoundVolume = 0.10f;
static const int kAudioFrameMs = 10; // Each audio frame is exactly 10ms.
static const int kAudioPacketsPerSecond = 1000 / kAudioFrameMs;

// Bounds for variable frame size mode.
static const int kMinFakeFrameWidth = 60;
static const int kMinFakeFrameHeight = 34;
static const int kStartingFakeFrameWidth = 854;
static const int kStartingFakeFrameHeight = 480;
static const int kMaxFakeFrameWidth = 1280;
static const int kMaxFakeFrameHeight = 720;
static const int kMaxFrameSizeChangeMillis = 5000;

void AVFreeFrame(AVFrame* frame)
{
    av_frame_free(&frame);
}

base::TimeDelta PtsToTimeDelta(int64_t pts, const AVRational& time_base)
{
    return pts * base::TimeDelta::FromSeconds(1) * time_base.num / time_base.den;
}

int64_t TimeDeltaToPts(base::TimeDelta delta, const AVRational& time_base)
{
    return static_cast<int64_t>(
        delta.InSecondsF() * time_base.den / time_base.num + 0.5 /* rounding */);
}

} // namespace

namespace media {
namespace cast {

    FakeMediaSource::FakeMediaSource(
        scoped_refptr<base::SingleThreadTaskRunner> task_runner,
        base::TickClock* clock,
        const FrameSenderConfig& audio_config,
        const FrameSenderConfig& video_config,
        bool keep_frames)
        : task_runner_(task_runner)
        , output_audio_params_(AudioParameters::AUDIO_PCM_LINEAR,
              media::GuessChannelLayout(audio_config.channels),
              audio_config.rtp_timebase,
              32,
              audio_config.rtp_timebase / kAudioPacketsPerSecond)
        , video_config_(video_config)
        , keep_frames_(keep_frames)
        , variable_frame_size_mode_(false)
        , synthetic_count_(0)
        , clock_(clock)
        , audio_frame_count_(0)
        , video_frame_count_(0)
        , av_format_context_(NULL)
        , audio_stream_index_(-1)
        , playback_rate_(1.0)
        , video_stream_index_(-1)
        , video_frame_rate_numerator_(video_config.max_frame_rate)
        , video_frame_rate_denominator_(1)
        , video_first_pts_(0)
        , video_first_pts_set_(false)
        , weak_factory_(this)
    {
        CHECK(output_audio_params_.IsValid());
        audio_bus_factory_.reset(
            new TestAudioBusFactory(audio_config.channels, audio_config.rtp_timebase,
                kSoundFrequency, kSoundVolume));
    }

    FakeMediaSource::~FakeMediaSource()
    {
    }

    void FakeMediaSource::SetSourceFile(const base::FilePath& video_file,
        int final_fps)
    {
        DCHECK(!video_file.empty());

        LOG(INFO) << "Source: " << video_file.value();
        if (!file_data_.Initialize(video_file)) {
            LOG(ERROR) << "Cannot load file.";
            return;
        }
        protocol_.reset(
            new InMemoryUrlProtocol(file_data_.data(), file_data_.length(), false));
        glue_.reset(new FFmpegGlue(protocol_.get()));

        if (!glue_->OpenContext()) {
            LOG(ERROR) << "Cannot open file.";
            return;
        }

        // AVFormatContext is owned by the glue.
        av_format_context_ = glue_->format_context();
        if (avformat_find_stream_info(av_format_context_, NULL) < 0) {
            LOG(ERROR) << "Cannot find stream information.";
            return;
        }

        // Prepare FFmpeg decoders.
        for (unsigned int i = 0; i < av_format_context_->nb_streams; ++i) {
            AVStream* av_stream = av_format_context_->streams[i];
            std::unique_ptr<AVCodecContext, ScopedPtrAVFreeContext> av_codec_context(
                AVStreamToAVCodecContext(av_stream));
            if (!av_codec_context) {
                LOG(ERROR) << "Cannot get a codec context for the codec: "
                           << av_stream->codecpar->codec_id;
                continue;
            }

            AVCodec* av_codec = avcodec_find_decoder(av_codec_context->codec_id);

            if (!av_codec) {
                LOG(ERROR) << "Cannot find decoder for the codec: "
                           << av_codec_context->codec_id;
                continue;
            }

            // Number of threads for decoding.
            av_codec_context->thread_count = 2;
            av_codec_context->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
            av_codec_context->request_sample_fmt = AV_SAMPLE_FMT_S16;

            if (avcodec_open2(av_codec_context.get(), av_codec, nullptr) < 0) {
                LOG(ERROR) << "Cannot open AVCodecContext for the codec: "
                           << av_codec_context->codec_id;
                return;
            }

            if (av_codec->type == AVMEDIA_TYPE_AUDIO) {
                if (av_codec_context->sample_fmt == AV_SAMPLE_FMT_S16P) {
                    LOG(ERROR) << "Audio format not supported.";
                    continue;
                }
                ChannelLayout layout = ChannelLayoutToChromeChannelLayout(
                    av_codec_context->channel_layout,
                    av_codec_context->channels);
                if (layout == CHANNEL_LAYOUT_UNSUPPORTED) {
                    LOG(ERROR) << "Unsupported audio channels layout.";
                    continue;
                }
                if (audio_stream_index_ != -1) {
                    LOG(WARNING) << "Found multiple audio streams.";
                }
                audio_stream_index_ = static_cast<int>(i);
                av_audio_context_ = std::move(av_codec_context);
                source_audio_params_.Reset(
                    AudioParameters::AUDIO_PCM_LINEAR, layout,
                    av_audio_context_->sample_rate,
                    8 * av_get_bytes_per_sample(av_audio_context_->sample_fmt),
                    av_audio_context_->sample_rate / kAudioPacketsPerSecond);
                source_audio_params_.set_channels_for_discrete(
                    av_audio_context_->channels);
                CHECK(source_audio_params_.IsValid());
                LOG(INFO) << "Source file has audio.";
            } else if (av_codec->type == AVMEDIA_TYPE_VIDEO) {
                VideoPixelFormat format = AVPixelFormatToVideoPixelFormat(av_codec_context->pix_fmt);
                if (format != PIXEL_FORMAT_YV12) {
                    LOG(ERROR) << "Cannot handle non YV12 video format: " << format;
                    continue;
                }
                if (video_stream_index_ != -1) {
                    LOG(WARNING) << "Found multiple video streams.";
                }
                video_stream_index_ = static_cast<int>(i);
                av_video_context_ = std::move(av_codec_context);
                if (final_fps > 0) {
                    // If video is played at a manual speed audio needs to match.
                    playback_rate_ = 1.0 * final_fps * av_stream->r_frame_rate.den / av_stream->r_frame_rate.num;
                    video_frame_rate_numerator_ = final_fps;
                    video_frame_rate_denominator_ = 1;
                } else {
                    playback_rate_ = 1.0;
                    video_frame_rate_numerator_ = av_stream->r_frame_rate.num;
                    video_frame_rate_denominator_ = av_stream->r_frame_rate.den;
                }
                LOG(INFO) << "Source file has video.";
            } else {
                LOG(ERROR) << "Unknown stream type; ignore.";
            }
        }

        Rewind();
    }

    void FakeMediaSource::SetVariableFrameSizeMode(bool enabled)
    {
        variable_frame_size_mode_ = enabled;
    }

    void FakeMediaSource::Start(scoped_refptr<AudioFrameInput> audio_frame_input,
        scoped_refptr<VideoFrameInput> video_frame_input)
    {
        audio_frame_input_ = audio_frame_input;
        video_frame_input_ = video_frame_input;

        LOG(INFO) << "Max Frame rate: " << video_config_.max_frame_rate;
        LOG(INFO) << "Source Frame rate: "
                  << video_frame_rate_numerator_ << "/"
                  << video_frame_rate_denominator_ << " fps.";
        LOG(INFO) << "Audio playback rate: " << playback_rate_;

        if (start_time_.is_null())
            start_time_ = clock_->NowTicks();

        if (!is_transcoding_audio() && !is_transcoding_video()) {
            // Send fake patterns.
            task_runner_->PostTask(
                FROM_HERE,
                base::Bind(&FakeMediaSource::SendNextFakeFrame,
                    weak_factory_.GetWeakPtr()));
            return;
        }

        // Send transcoding streams.
        audio_algo_.Initialize(source_audio_params_);
        audio_algo_.FlushBuffers();
        audio_fifo_input_bus_ = AudioBus::Create(
            source_audio_params_.channels(),
            source_audio_params_.frames_per_buffer());
        // Audio FIFO can carry all data fron AudioRendererAlgorithm.
        audio_fifo_.reset(
            new AudioFifo(source_audio_params_.channels(),
                audio_algo_.QueueCapacity()));
        audio_converter_.reset(new media::AudioConverter(
            source_audio_params_, output_audio_params_, true));
        audio_converter_->AddInput(this);
        task_runner_->PostTask(
            FROM_HERE,
            base::Bind(&FakeMediaSource::SendNextFrame, weak_factory_.GetWeakPtr()));
    }

    void FakeMediaSource::SendNextFakeFrame()
    {
        UpdateNextFrameSize();
        scoped_refptr<VideoFrame> video_frame = VideoFrame::CreateBlackFrame(current_frame_size_);
        PopulateVideoFrame(video_frame.get(), synthetic_count_);
        ++synthetic_count_;

        const base::TimeTicks now = clock_->NowTicks();

        base::TimeDelta video_time = VideoFrameTime(++video_frame_count_);
        video_frame->set_timestamp(video_time);
        if (keep_frames_)
            inserted_video_frame_queue_.push(video_frame);
        video_frame_input_->InsertRawVideoFrame(video_frame,
            start_time_ + video_time);

        // Send just enough audio data to match next video frame's time.
        base::TimeDelta audio_time = AudioFrameTime(audio_frame_count_);
        while (audio_time < video_time) {
            if (is_transcoding_audio()) {
                Decode(true);
                CHECK(!audio_bus_queue_.empty()) << "No audio decoded.";
                std::unique_ptr<AudioBus> bus(audio_bus_queue_.front());
                audio_bus_queue_.pop();
                audio_frame_input_->InsertAudio(std::move(bus), start_time_ + audio_time);
            } else {
                audio_frame_input_->InsertAudio(
                    audio_bus_factory_->NextAudioBus(
                        base::TimeDelta::FromMilliseconds(kAudioFrameMs)),
                    start_time_ + audio_time);
            }
            audio_time = AudioFrameTime(++audio_frame_count_);
        }

        // This is the time since FakeMediaSource was started.
        const base::TimeDelta elapsed_time = now - start_time_;

        // Handle the case when frame generation cannot keep up.
        // Move the time ahead to match the next frame.
        while (video_time < elapsed_time) {
            LOG(WARNING) << "Skipping one frame.";
            video_time = VideoFrameTime(++video_frame_count_);
        }

        task_runner_->PostDelayedTask(
            FROM_HERE,
            base::Bind(&FakeMediaSource::SendNextFakeFrame,
                weak_factory_.GetWeakPtr()),
            video_time - elapsed_time);
    }

    void FakeMediaSource::UpdateNextFrameSize()
    {
        if (variable_frame_size_mode_) {
            bool update_size_change_time = false;
            if (current_frame_size_.IsEmpty()) {
                current_frame_size_ = gfx::Size(kStartingFakeFrameWidth,
                    kStartingFakeFrameHeight);
                update_size_change_time = true;
            } else if (clock_->NowTicks() >= next_frame_size_change_time_) {
                current_frame_size_ = gfx::Size(
                    base::RandInt(kMinFakeFrameWidth, kMaxFakeFrameWidth),
                    base::RandInt(kMinFakeFrameHeight, kMaxFakeFrameHeight));
                update_size_change_time = true;
            }

            if (update_size_change_time) {
                next_frame_size_change_time_ = clock_->NowTicks() + base::TimeDelta::FromMillisecondsD(base::RandDouble() * kMaxFrameSizeChangeMillis);
            }
        } else {
            current_frame_size_ = gfx::Size(kStartingFakeFrameWidth,
                kStartingFakeFrameHeight);
            next_frame_size_change_time_ = base::TimeTicks();
        }
    }

    bool FakeMediaSource::SendNextTranscodedVideo(base::TimeDelta elapsed_time)
    {
        if (!is_transcoding_video())
            return false;

        Decode(false);
        if (video_frame_queue_.empty())
            return false;

        const scoped_refptr<VideoFrame> video_frame = video_frame_queue_.front();
        if (elapsed_time < video_frame->timestamp())
            return false;
        video_frame_queue_.pop();

        // Use the timestamp from the file if we're transcoding.
        video_frame->set_timestamp(ScaleTimestamp(video_frame->timestamp()));
        if (keep_frames_)
            inserted_video_frame_queue_.push(video_frame);
        video_frame_input_->InsertRawVideoFrame(
            video_frame, start_time_ + video_frame->timestamp());

        // Make sure queue is not empty.
        Decode(false);
        return true;
    }

    bool FakeMediaSource::SendNextTranscodedAudio(base::TimeDelta elapsed_time)
    {
        if (!is_transcoding_audio())
            return false;

        Decode(true);
        if (audio_bus_queue_.empty())
            return false;

        base::TimeDelta audio_time = audio_sent_ts_->GetTimestamp();
        if (elapsed_time < audio_time)
            return false;
        std::unique_ptr<AudioBus> bus(audio_bus_queue_.front());
        audio_bus_queue_.pop();
        audio_sent_ts_->AddFrames(bus->frames());
        audio_frame_input_->InsertAudio(std::move(bus), start_time_ + audio_time);

        // Make sure queue is not empty.
        Decode(true);
        return true;
    }

    void FakeMediaSource::SendNextFrame()
    {
        // Send as much as possible. Audio is sent according to
        // system time.
        while (SendNextTranscodedAudio(clock_->NowTicks() - start_time_)) {
        }

        // Video is sync'ed to audio.
        while (SendNextTranscodedVideo(audio_sent_ts_->GetTimestamp())) {
        }

        if (audio_bus_queue_.empty() && video_frame_queue_.empty()) {
            // Both queues are empty can only mean that we have reached
            // the end of the stream.
            LOG(INFO) << "Rewind.";
            Rewind();
        }

        // Send next send.
        task_runner_->PostDelayedTask(
            FROM_HERE,
            base::Bind(&FakeMediaSource::SendNextFrame, weak_factory_.GetWeakPtr()),
            base::TimeDelta::FromMilliseconds(kAudioFrameMs));
    }

    base::TimeDelta FakeMediaSource::VideoFrameTime(int frame_number)
    {
        return frame_number * base::TimeDelta::FromSeconds(1) * video_frame_rate_denominator_ / video_frame_rate_numerator_;
    }

    base::TimeDelta FakeMediaSource::ScaleTimestamp(base::TimeDelta timestamp)
    {
        return base::TimeDelta::FromSecondsD(timestamp.InSecondsF() / playback_rate_);
    }

    base::TimeDelta FakeMediaSource::AudioFrameTime(int frame_number)
    {
        return frame_number * base::TimeDelta::FromMilliseconds(kAudioFrameMs);
    }

    void FakeMediaSource::Rewind()
    {
        CHECK(av_seek_frame(av_format_context_, -1, 0, AVSEEK_FLAG_BACKWARD) >= 0)
            << "Failed to rewind to the beginning.";
    }

    ScopedAVPacket FakeMediaSource::DemuxOnePacket(bool* audio)
    {
        ScopedAVPacket packet(new AVPacket());
        if (av_read_frame(av_format_context_, packet.get()) < 0) {
            VLOG(1) << "Failed to read one AVPacket.";
            packet.reset();
            return packet;
        }

        int stream_index = static_cast<int>(packet->stream_index);
        if (stream_index == audio_stream_index_) {
            *audio = true;
        } else if (stream_index == video_stream_index_) {
            *audio = false;
        } else {
            // Ignore unknown packet.
            LOG(INFO) << "Unknown packet.";
            packet.reset();
        }
        return packet;
    }

    void FakeMediaSource::DecodeAudio(ScopedAVPacket packet)
    {
        // Audio.
        AVFrame* avframe = av_frame_alloc();

        // Make a shallow copy of packet so we can slide packet.data as frames are
        // decoded from the packet; otherwise av_packet_unref() will corrupt memory.
        AVPacket packet_temp = *packet.get();

        do {
            int frame_decoded = 0;
            int result = avcodec_decode_audio4(av_audio_context_.get(), avframe,
                &frame_decoded, &packet_temp);
            CHECK(result >= 0) << "Failed to decode audio.";
            packet_temp.size -= result;
            packet_temp.data += result;
            if (!frame_decoded)
                continue;

            int frames_read = avframe->nb_samples;
            if (frames_read < 0)
                break;

            if (!audio_sent_ts_) {
                // Initialize the base time to the first packet in the file.
                // This is set to the frequency we send to the receiver.
                // Not the frequency of the source file. This is because we
                // increment the frame count by samples we sent.
                audio_sent_ts_.reset(
                    new AudioTimestampHelper(output_audio_params_.sample_rate()));
                // For some files this is an invalid value.
                base::TimeDelta base_ts;
                audio_sent_ts_->SetBaseTimestamp(base_ts);
            }

            scoped_refptr<AudioBuffer> buffer = AudioBuffer::CopyFrom(
                AVSampleFormatToSampleFormat(av_audio_context_->sample_fmt,
                    av_audio_context_->codec_id),
                ChannelLayoutToChromeChannelLayout(av_audio_context_->channel_layout,
                    av_audio_context_->channels),
                av_audio_context_->channels, av_audio_context_->sample_rate,
                frames_read, &avframe->data[0],
                PtsToTimeDelta(avframe->pts, av_audio_stream()->time_base));
            audio_algo_.EnqueueBuffer(buffer);
            av_frame_unref(avframe);
        } while (packet_temp.size > 0);
        av_frame_free(&avframe);

        const int frames_needed_to_scale = playback_rate_ * av_audio_context_->sample_rate / kAudioPacketsPerSecond;
        while (frames_needed_to_scale <= audio_algo_.frames_buffered()) {
            if (!audio_algo_.FillBuffer(audio_fifo_input_bus_.get(), 0,
                    audio_fifo_input_bus_->frames(),
                    playback_rate_)) {
                // Nothing can be scaled. Decode some more.
                return;
            }

            // Prevent overflow of audio data in the FIFO.
            if (audio_fifo_input_bus_->frames() + audio_fifo_->frames()
                <= audio_fifo_->max_frames()) {
                audio_fifo_->Push(audio_fifo_input_bus_.get());
            } else {
                LOG(WARNING) << "Audio FIFO full; dropping samples.";
            }

            // Make sure there's enough data to resample audio.
            if (audio_fifo_->frames() < 2 * source_audio_params_.sample_rate() / kAudioPacketsPerSecond) {
                continue;
            }

            std::unique_ptr<media::AudioBus> resampled_bus(media::AudioBus::Create(
                output_audio_params_.channels(),
                output_audio_params_.sample_rate() / kAudioPacketsPerSecond));
            audio_converter_->Convert(resampled_bus.get());
            audio_bus_queue_.push(resampled_bus.release());
        }
    }

    void FakeMediaSource::DecodeVideo(ScopedAVPacket packet)
    {
        // Video.
        int got_picture;
        AVFrame* avframe = av_frame_alloc();
        CHECK(avcodec_decode_video2(av_video_context_.get(), avframe, &got_picture,
                  packet.get())
            >= 0)
            << "Video decode error.";
        if (!got_picture) {
            av_frame_free(&avframe);
            return;
        }
        gfx::Size size(av_video_context_->width, av_video_context_->height);

        if (!video_first_pts_set_) {
            video_first_pts_ = avframe->pts;
            video_first_pts_set_ = true;
        }
        const AVRational& time_base = av_video_stream()->time_base;
        base::TimeDelta timestamp = PtsToTimeDelta(avframe->pts - video_first_pts_, time_base);
        if (timestamp < last_video_frame_timestamp_) {
            // Stream has rewound.  Rebase |video_first_pts_|.
            const AVRational& frame_rate = av_video_stream()->r_frame_rate;
            timestamp = last_video_frame_timestamp_ + (base::TimeDelta::FromSeconds(1) * frame_rate.den / frame_rate.num);
            const int64_t adjustment_pts = TimeDeltaToPts(timestamp, time_base);
            video_first_pts_ = avframe->pts - adjustment_pts;
        }

        scoped_refptr<media::VideoFrame> video_frame = VideoFrame::WrapExternalYuvData(
            media::PIXEL_FORMAT_YV12, size, gfx::Rect(size), size,
            avframe->linesize[0], avframe->linesize[1], avframe->linesize[2],
            avframe->data[0], avframe->data[1], avframe->data[2], timestamp);
        if (!video_frame)
            return;
        video_frame_queue_.push(video_frame);
        video_frame_queue_.back()->AddDestructionObserver(
            base::Bind(&AVFreeFrame, avframe));
        last_video_frame_timestamp_ = timestamp;
    }

    void FakeMediaSource::Decode(bool decode_audio)
    {
        // Read the stream until one video frame can be decoded.
        while (true) {
            if (decode_audio && !audio_bus_queue_.empty())
                return;
            if (!decode_audio && !video_frame_queue_.empty())
                return;

            bool audio_packet = false;
            ScopedAVPacket packet = DemuxOnePacket(&audio_packet);
            if (!packet) {
                VLOG(1) << "End of stream.";
                return;
            }

            if (audio_packet)
                DecodeAudio(std::move(packet));
            else
                DecodeVideo(std::move(packet));
        }
    }

    double FakeMediaSource::ProvideInput(media::AudioBus* output_bus,
        uint32_t frames_delayed)
    {
        if (audio_fifo_->frames() >= output_bus->frames()) {
            audio_fifo_->Consume(output_bus, 0, output_bus->frames());
            return 1.0;
        } else {
            LOG(WARNING) << "Not enough audio data for resampling.";
            output_bus->Zero();
            return 0.0;
        }
    }

    scoped_refptr<media::VideoFrame>
    FakeMediaSource::PopOldestInsertedVideoFrame()
    {
        CHECK(!inserted_video_frame_queue_.empty());
        scoped_refptr<media::VideoFrame> video_frame = inserted_video_frame_queue_.front();
        inserted_video_frame_queue_.pop();
        return video_frame;
    }

    AVStream* FakeMediaSource::av_audio_stream()
    {
        return av_format_context_->streams[audio_stream_index_];
    }

    AVStream* FakeMediaSource::av_video_stream()
    {
        return av_format_context_->streams[video_stream_index_];
    }

} // namespace cast
} // namespace media
